﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace Whoot
{
    public enum TicketType
    {
        Update = 1,
        LateUpdate = 2,
        FixedUpdate = 3,
        Delay = 4,
    }

    public interface ITimeTicket : IDisposable
    {
        int? Id { get; }
        TicketType Type { get; }


        void Unregister();
    }


    public readonly struct TimeTicket : ITimeTicket
    {
        public int? Id { get; }
        public TicketType Type { get; }

        public TimeTicket(int id, TicketType type)
        {
            this.Id = id;
            this.Type = type;
        }

        public void Unregister()
        {
            if (Id == null) return;
            switch (this.Type)
            {
                case TicketType.Update:
                    Timer.RemoveUpdate(Id.Value);
                    break;
                case TicketType.LateUpdate:
                    Timer.RemoveLateUpdate(Id.Value);
                    break;
                case TicketType.FixedUpdate:
                    Timer.RemoveFixedUpdate(Id.Value);
                    break;
                case TicketType.Delay:
                    Timer.RemoveFixedUpdate(Id.Value);
                    break;
            }
        }

        public override bool Equals(object obj)
        {
            return obj is TimeTicket ticket &&
                   Id == ticket.Id &&
                   Type == ticket.Type;
        }

        public override int GetHashCode()
        {
            var hashCode = -1056084179;
            hashCode = hashCode * -1521134295 + Id.GetHashCode();
            hashCode = hashCode * -1521134295 + Type.GetHashCode();
            return hashCode;
        }

        public static bool operator ==(TimeTicket left, TimeTicket right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(TimeTicket left, TimeTicket right)
        {
            return !(left == right);
        }

        public void Dispose()
        {
            this.Unregister();
        }
    }


    public class Timer
    {
        public delegate void DelayCallback(System.Object obj);

        private static Timer _instance;
        internal static Timer Instance => _instance ?? (_instance = new Timer());

        public Timer()
        {
            MonoManager.Inst.OnUpdate += OnUpdate;
            MonoManager.Inst.OnLateUpdate += OnLateUpdate;
            MonoManager.Inst.OnFixedUpdate += OnFixedUpdate;
        }

        internal class DelayActionData
        {
            public DelayCallback DelayCallback;
            public float Delay;
            public bool Loop;
            public bool UnTimeScale;
            public System.Object Owner;

            /// <summary>
            /// 当前已经运行多久了
            /// </summary>
            private float _timeAccum = 0;

            /// <summary>
            /// 是否开始计时了
            /// </summary>
            private bool _isAccuming = false;

            public DelayActionData(DelayCallback action, float delay, bool loop = false,
                bool unTimeScale = false,
                System.Object owner = null)
            {
                this.Loop = loop;
                this.UnTimeScale = unTimeScale;
                this.Owner = owner;
                StartCounting(delay, action);
            }

            private void StartCounting(float time, DelayCallback delayCallback)
            {
                _timeAccum = 0;
                Delay = time;
                _isAccuming = true;
                DelayCallback = delayCallback;
            }

            public void OnUpdate()
            {
                if (!_isAccuming) return;
                if (UnTimeScale)
                {
                    _timeAccum += Time.unscaledDeltaTime;
                }
                else
                {
                    _timeAccum += Time.deltaTime;
                }

                _isAccuming = !(_timeAccum > Delay);
                if (_isAccuming) return;
                DelayCallback?.Invoke(Owner);
                Instance?.RemoveDelayAction(DelayCallback);

                if (!Loop) return;
                StartCounting(Delay, DelayCallback);
                Instance?.AddDelayAction(Delay, DelayCallback, Loop, UnTimeScale, Owner);
            }
        }


        public static ITimeTicket AddUpdate(Action updateAction)
        {
            return Instance.AddUpdateAction(updateAction);
        }

        public static void RemoveUpdate(Action updateAction)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveUpdateAction(updateAction);
        }

        public static void RemoveUpdate(int actionId)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveUpdateAction(actionId);
        }

        public static ITimeTicket AddLateUpdate(Action updateAction)
        {
            return Instance.AddLateUpdateAction(updateAction);
        }

        public static void RemoveLateUpdate(Action updateAction)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveLateUpdateAction(updateAction);
        }

        public static void RemoveLateUpdate(int actionId)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveLateUpdateAction(actionId);
        }


        public static ITimeTicket AddFixedUpdate(Action updateAction)
        {
            return Instance.AddFixedUpdateAction(updateAction);
        }

        public static void RemoveFixedUpdate(Action updateAction)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveFixedUpdateAction(updateAction);
        }

        public static void RemoveFixedUpdate(int actionId)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveFixedUpdateAction(actionId);
        }


        /// <summary>
        /// 添加一个计时
        /// </summary>
        /// <param name="delay">时间</param>
        /// <param name="delayAction">回调</param>
        /// <param name="loop">是否循环</param>
        /// <param name="unTimeScale">是否忽略TimeScale</param>
        /// <param name="obj">对象</param>
        /// <returns></returns>
        public static ITimeTicket AddDelay(float delay, DelayCallback delayAction, bool loop = false,
            bool unTimeScale = false, System.Object owner = null)
        {
            return Instance.AddDelayAction(delay, delayAction, loop, unTimeScale, owner);
        }

        public static void RemoveDelay(DelayCallback updateAction)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveDelayAction(updateAction);
        }

        public static void RemoveDelay(int actionId)
        {
            if (!Application.isPlaying) return;
            Instance.RemoveDelayAction(actionId);
        }

        #region 实现

        //****************   Update   *******************

        private readonly Dictionary<int, Action> _updateHashAction = new Dictionary<int, Action>();

        private ITimeTicket AddUpdateAction(Action updateAction)
        {
            lock (this)
            {
                if (updateAction == null) return default;
                var hashCode = updateAction.GetHashCode();
                if (_updateHashAction.TryGetValue(hashCode, out var action))
                {
                    return new TimeTicket(hashCode, TicketType.Update);
                }
                else
                {
                    _updateHashAction.Add(hashCode, updateAction);

                    return new TimeTicket(hashCode, TicketType.Update);
                }
            }
        }

        public void RemoveUpdateAction(Action updateAction)
        {
            if (updateAction == null) return;
            this.RemoveUpdateAction(updateAction.GetHashCode());
        }

        public void RemoveUpdateAction(int actionId)
        {
            lock (this)
            {
                if (_updateHashAction.ContainsKey(actionId))
                {
                    _updateHashAction.Remove(actionId);
                }
            }
        }

        //****************   Late Update   *******************
        private readonly Dictionary<int, Action> _updateLateHashAction = new Dictionary<int, Action>();

        private ITimeTicket AddLateUpdateAction(Action updateAction)
        {
            lock (this)
            {
                if (updateAction == null) return default;
                var hashCode = updateAction.GetHashCode();
                if (_updateLateHashAction.TryGetValue(hashCode, out var action))
                {
                    return new TimeTicket(hashCode, TicketType.LateUpdate);
                }
                else
                {
                    _updateLateHashAction.Add(hashCode, updateAction);

                    return new TimeTicket(hashCode, TicketType.LateUpdate);
                }
            }
        }

        public void RemoveLateUpdateAction(Action updateAction)
        {
            if (updateAction == null) return;
            this.RemoveLateUpdateAction(updateAction.GetHashCode());
        }

        public void RemoveLateUpdateAction(int actionId)
        {
            lock (this)
            {
                if (_updateLateHashAction.ContainsKey(actionId))
                {
                    _updateLateHashAction.Remove(actionId);
                }
            }
        }

        //****************   Fixed Update   *******************
        private readonly Dictionary<int, Action> _updateFixedHashAction = new Dictionary<int, Action>();

        private ITimeTicket AddFixedUpdateAction(Action updateAction)
        {
            lock (this)
            {
                if (updateAction == null) return default;
                var hashCode = updateAction.GetHashCode();
                if (_updateFixedHashAction.TryGetValue(hashCode, out var action))
                {
                    return new TimeTicket(hashCode, TicketType.FixedUpdate);
                }
                else
                {
                    _updateFixedHashAction.Add(hashCode, updateAction);

                    return new TimeTicket(hashCode, TicketType.FixedUpdate);
                }
            }
        }

        public void RemoveFixedUpdateAction(Action updateAction)
        {
            if (updateAction == null) return;
            this.RemoveFixedUpdateAction(updateAction.GetHashCode());
        }

        public void RemoveFixedUpdateAction(int actionId)
        {
            lock (this)
            {
                if (_updateFixedHashAction.ContainsKey(actionId))
                {
                    _updateFixedHashAction.Remove(actionId);
                }
            }
        }

        //****************   Delay   *******************
        private readonly Dictionary<int, DelayActionData> _delayHashAction = new Dictionary<int, DelayActionData>();
        private readonly List<DelayActionData> _delayHashActionList = new List<DelayActionData>();

        public ITimeTicket AddDelayAction(float delay, DelayCallback delayAction, bool loop = false,
            bool unTimeScale = false, System.Object owner = null)
        {
            lock (this)
            {
                if (delayAction == null) return default;
                var hashCode = delayAction.GetHashCode();
                if (_delayHashAction.TryGetValue(hashCode, out var action))
                {
                    return new TimeTicket(hashCode, TicketType.Delay);
                }
                else
                {
                    var data = new DelayActionData(delayAction, delay, loop, unTimeScale, owner);
                    _delayHashAction.Add(hashCode, data);
                    _delayHashActionList.Add(data);

                    return new TimeTicket(hashCode, TicketType.Delay);
                }
            }
        }

        public void RemoveDelayAction(DelayCallback updateAction)
        {
            if (updateAction == null) return;
            this.RemoveDelayAction(updateAction.GetHashCode());
        }

        public void RemoveDelayAction(int actionId)
        {
            lock (this)
            {
                if (_delayHashAction.ContainsKey(actionId))
                {
                    var data = _delayHashAction[actionId];
                    _delayHashActionList.Remove(data);
                    _delayHashAction.Remove(actionId);
                }
            }
        }

        #endregion


        #region Update

        private void OnUpdate()
        {
            if (_delayHashAction.Count > 0)
            {
                for (var i = 0; i < _delayHashActionList.Count; i++)
                {
                    _delayHashActionList[i]?.OnUpdate();
                }
            }

            if (_updateHashAction.Count > 0)
            {
                foreach (var item in _updateHashAction.Values)
                {
                    item?.Invoke();
                }
            }
        }

        private void OnLateUpdate()
        {
            if (_updateLateHashAction.Count <= 0) return;
            foreach (var item in _updateLateHashAction.Values)
            {
                item?.Invoke();
            }
        }

        private void OnFixedUpdate()
        {
            if (_updateFixedHashAction.Count <= 0) return;
            foreach (var item in _updateFixedHashAction.Values)
            {
                item?.Invoke();
            }
        }

        #endregion

        // public event Action onLateUpdate;
        // public event Action onFixedUpdate;
    }
}